Spring IOC和Spring AOP

Spring IOC

IOC原理

IOC的意思是控件反转也就是由容器控制程序之间的关系,这也是spring的优点所在,把控件权交给了外部容器。之前的写法,由程序代码直接操控,而现在控制权由应用代码中转到了外部容器。控制权的转移是所谓反转。换句话说之前用new的方式获取对象,现在由Spring给你,至于怎么给你就是DI了。

什么是DI机制(依赖注入)

这里说DI又要说到IOC,依赖注入(Dependecy Injection)和控制反转(Inversion of Control)是同一个概念。具体的讲:当某个角色需要另外一个角色协助的时候,在传统的程序设计过程中,通常由调用者来创建被调用者的实例。但是在Spring中 创建被调用者的工作不再由调用者来完成,因此称为控制反转。在Spring中创建被调用者的工作由Spring来完成,然后注入调用者 因此也称为依赖注入。

Spring以动态灵活的方式来管理对象 , 注入的四种方式:

  1. 构造方法注入

  2. 注解注入

  3. setter注入

    注意:如果通过set方法注入属性,那么spring会通过默认的空参构造方法来实例化对象,所以如果在类中写了一个带有参数的构造方法,一定要把空参数的构造方法写上,否则spring没有办法实例化对象,导致报错。

IOC的实现原理

Java反射机制

概念

我们考虑一个场景,如果我们在程序运行时,一个对象想要检视自己所拥有的成员属性,该如何操作?

再考虑另一个场景,如果我们想要在运行期获得某个类的Class信息如它的属性、构造方法、一般方法后再考虑是否创建它的对象,这种情况该怎么办呢?这就需要用到反射!

对于反射,官方给出的概念:反射是Java语言的一个特性,它允许程序在运行时(注意不是编译的时候)来进行自我检查并且对内部的成员进行操作。例如它允许一个Java类获取它所有的成员变量和方法并且显示出来。

反射主要是指程序可以访问,检测和修改它本身状态或行为的一种能力,并能根据自身行为的状态和结果,调整或修改应用所描述行为的状态和相关的语义。在Java中,只要给定类的名字,那么就可以通过反射机制来获得类的所有信息。

反射是Java中一种强大的工具,能够使我们很方便的创建灵活的代码,这些代码可以在运行时装配,无需在组件之间进行源代码链接。但是如果反射使用不当会导致高成本!类中有什么信息,利用反射机制就能可以获得什么信息,不过前提是得知道类的名字

作用

  1. 在运行时判断任意一个对象所属的类;
  2. 在运行时获取类的对象
  3. 在运行时访问java对象的属性,方法,构造方法等。

首先要搞清楚为什么要用反射机制?直接创建对象不就可以了吗,这就涉及到了动态与静态的概念。

静态编译:在编译时确定类型,绑定对象,即通过。

动态编译:运行时确定类型,绑定对象。动态编译最大限度发挥了Java的灵活性,体现了多态的应用,有以降低类之间的藕合性。

反射机制的深度解析

Java的反射机制的实现要借助于4个类:class,Constructor,Field,Method;

Class:类对象,
Constructor-类的构造器对象,
Field-类的属性对象,
Method-类的方法对象。

Class反射对象描述类语义结构,可以从Class对象中获取构造函数、成员变量、方法类等类元素的反射对象,并以编程的方式通过这些反射对象对目标类对象进行操作。这些反射对象类在java.reflect包中定义,下面是最主要的三个反射类:

  • Constructor:类的构造函数反射类

    通过Class.getDeclaredConstructors()方法可以获得类的所有构造函数反射对象数组。

    在JDK5.0中,还可以通过getDeclaredConstructor(Class… parameterTypes)获取拥有特定入参的构造函数反射对象。

    Constructor的一个主要方法是newInstance(Object[] initargs),通过该方法可以创建一个对象类的实例,相当于new关键字。在JDK5.0中该方法演化为更为灵活的形式:newInstance (Object… initargs)。

  • Method:类方法的反射类

    通过Class.getDeclaredMethods()方法可以获取类的所有方法反射类对象数组Method[]。

    在JDK5.0中可以通过getDeclaredMethod(String name, Class… parameterTypes)获取特定方法,name为方法名,Class…为方法入参类型列表。

    Method最主要的方法是invoke(Object obj, Object[] args),obj表示操作的目标对象,args为方法入参。

  • Field:类的成员变量的反射类

    通过Class.getDeclaredFields()方法可以获取类的成员变量反射对象数组

    通过Class.getDeclaredField(String name)则可获取某个特定名称的成员变量反射对象。

    Field类最主要的方法是set(Object obj, Object value),obj表示操作的目标对象,通过value为目标对象的成员变量设置值。如果成员变量为基础类型,用户可以使用Field类中提供的带类型名的值设置方法,比如setBoolean(Object obj, boolean value)、setInt(Object obj, int value)等。

基本运用

  1. 获取Class对象

    • 使用 Class 类的 forName 静态方法:

      1
      2
      3
      4
      public static Class<?> forName(String className)
      ```
      比如在 JDBC 开发中常用此方法加载数据库驱动:
      Class.forName(driver);
    • 直接获取某一个对象的 class,比如:

      1
      2
      Class<?> klass = int.class;
      Class<?> classInt = Integer.TYPE;
    • 调用某个对象的 getClass() 方法,比如:

      1
      2
      StringBuilder str = new StringBuilder("123");
      Class<?> klass = str.getClass();
  2. 判断是否为某个类的实例

    一般地,我们用 instanceof 关键字来判断是否为某个类的实例。同时我们也可以借助反射中 Class 对象的 isInstance() 方法来判断是否为某个类的实例,它是一个 native 方法:

    1
    public native boolean isInstance(Object obj);
  3. 创建实例对象

    • 使用Class对象的newInstance()方法来创建Class对象对应类的实例

      1
      2
      Class<?> c = String.class;
      Object str = c.newInstance();
    • 先通过Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance()方法来创建实例。这种方法可以用指定的构造器构造类的实例

      1
      2
      3
      4
      5
      6
      7
      //获取String所对应的Class对象
      Class<?> c = String.class;
      //获取String类带一个String参数的构造器
      Constructor constructor = c.getConstructor(String.class);
      //根据构造器创建实例
      Object obj = constructor.newInstance("23333");
      System.out.println(obj);
  4. 获取方法

    获取某个Class对象的方法集合,主要有以下几个方法:

    • getDeclaredMethods 方法返回类或接口声明的所有方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法
    1
    public Method[] getDeclaredMethods() throws SecurityException
    • getMethods 方法返回某个类的所有公用(public)方法,包括其继承类的公用方法
    1
    public Method[] getMethods() throws SecurityException
    • getMethod 方法返回一个特定的方法,其中第一个参数为方法名称,后面的参数为方法的参数对应Class的对象。
    1
    public Method getMethod(String name, Class<?>... parameterTypes)

    例子如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    package org.ScZyhSoft.common;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    public class test1 {
    public static void test() throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
    Class<?> c = methodClass.class;
    Object object = c.newInstance();
    Method[] methods = c.getMethods();
    Method[] declaredMethods = c.getDeclaredMethods();
    //获取methodClass类的add方法
    Method method = c.getMethod("add", int.class, int.class);
    //getMethods()方法获取的所有方法
    System.out.println("getMethods获取的方法:");
    for(Method m:methods)
    System.out.println(m);
    //getDeclaredMethods()方法获取的所有方法
    System.out.println("getDeclaredMethods获取的方法:");
    for(Method m:declaredMethods)
    System.out.println(m);
    }
    }
    class methodClass {
    public final int fuck = 3;
    public int add(int a,int b) {
    return a+b;
    }
    public int sub(int a,int b) {
    return a+b;
    }
    }

    程序的运行结果如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    getMethods获取的方法:
    public int org.ScZyhSoft.common.methodClass.add(int,int)
    public int org.ScZyhSoft.common.methodClass.sub(int,int)
    public final void java.lang.Object.wait() throws java.lang.InterruptedException
    public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
    public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
    public boolean java.lang.Object.equals(java.lang.Object)
    public java.lang.String java.lang.Object.toString()
    public native int java.lang.Object.hashCode()
    public final native java.lang.Class java.lang.Object.getClass()
    public final native void java.lang.Object.notify()
    public final native void java.lang.Object.notifyAll()
    getDeclaredMethods获取的方法:
    public int org.ScZyhSoft.common.methodClass.add(int,int)
    public int org.ScZyhSoft.common.methodClass.sub(int,int)
  5. 获取构造器信息

    获取类构造器的用法与上述获取方法的用法类似。主要是通过Class类的getConstructor方法得到Constructor类的一个实例,而Constructor类有一个newInstance方法可以创建一个对象实例:

    1
    public T newInstance(Object ... initargs)
  6. 获取类的成员变量信息

    主要是这几个方法,在此不再赘述:

    • getFiled:访问公有的成员变量
    • getDeclaredField:所有已声明的成员变量,但不能得到其父类的成员变量

    getFiledsgetDeclaredFields 方法用法同上(参照 Method)

  7. 调用方法

    当我们从类中获取了一个方法后,我们就可以用 invoke() 方法来调用这个方法。invoke 方法的原型为:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public Object invoke(Object obj, Object... args)
    throws IllegalAccessException, IllegalArgumentException,
    InvocationTargetException
    {
    if (!override) {
    if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
    Class<?> caller = Reflection.getCallerClass();
    checkAccess(caller, clazz, obj, modifiers);
    }
    }
    MethodAccessor ma = methodAccessor; // read volatile
    if (ma == null) {
    ma = acquireMethodAccessor();
    }
    return ma.invoke(obj, args);
    }

    例如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    public class test1 {
    public static void main(String[] args) throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
    Class<?> klass = methodClass.class;
    //创建methodClass的实例
    Object obj = klass.newInstance();
    //获取methodClass类的add方法
    Method method = klass.getMethod("add",int.class,int.class);
    //调用method对应的方法 => add(1,4)
    Object result = method.invoke(obj,1,4);
    System.out.println(result);
    }
    }
    class methodClass {
    public final int fuck = 3;
    public int add(int a,int b) {
    return a+b;
    }
    public int sub(int a,int b) {
    return a+b;
    }
    }

IOC容器技术剖析

IOC中最基本的技术就是“反射(Reflection)”编程,通俗来讲就是根据给出的类名(字符串方式)来动态地生成对象,这种编程方式可以让对象在生成时才被决定到底是哪一种对象。只是在Spring中要生产的对象都在配置文件中给出定义,目的就是提高灵活性和可维护性。

目前C#、Java和PHP5等语言均支持反射,其中PHP5的技术书籍中,有时候也被翻译成“映射”。有关反射的概念和用法,大家应该都很清楚。反射的应用是很广泛的,很多的成熟的框架,比如像Java中的Hibernate、Spring框架,.Net中NHibernate、Spring.NET框架都是把”反射“做为最基本的技术手段。

我们可以把IOC容器的工作模式看做是工厂模式的升华,可以把IOC容器看作是一个工厂,这个工厂里要生产的对象都在配置文件中给出定义,然后利用编程语言提供的反射机制,根据配置文件中给出的类名生成相应的对象。从实现来看,IOC是把以前在工厂方法里写死的对象生成代码,改变为由配置文件来定义,也就是把工厂和对象生成这两者独立分隔开来,目的就是提高灵活性和可维护性。

工厂模式

Spring AOP

什么是AOP?

面向切面编程(AOP)完善Spring的依赖注入(DI),面向切面编程在spring中主要表现为两个方面:

  1. 面向切面编程提供声明式事务管理
  2. Spring支持用户自定义的切面

面向切面编程(aop)是对面向对象编程(oop)的补充。

面向对象编程将程序分解成各个层次的对象,面向切面编程将程序运行过程分解成各个切面。

AOP从程序运行角度考虑程序的结构,提取业务处理过程的切面,oop是静态的抽象,aop是动态的抽象, 是对应用执行过程中的步骤进行抽象,从而获得步骤之间的逻辑划分。

具体使用场景:

  1. Authentication:权限
  2. Caching:缓存
  3. Context passing:内容传递
  4. Error handling:错误处理
  5. Lazy loading:懒加载
  6. Debugging:调试
  7. logging, tracing, profiling and monitoring:记录跟踪 优化 校准
  8. Performance optimization:性能优化
  9. Persistence:持久化
  10. Resource pooling:资源池
  11. Synchronization:同步
  12. Transactions:事务

使用AOP需要的一些概念

  • Aspect(切面):Aspect 声明类似于 Java 中的类声明,在 Aspect 中会包含着一些 Pointcut 以及相应Advice。
  • Joint point(连接点):表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它 joint point。
  • Pointcut(切点):表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方。
  • Advice(增强):Advice 定义了在 Pointcut 里面定义的程序点具体要做的操作,它通过 before、after 和around 来区别是在每个 joint point 之前、之后还是代替执行的代码。
  • Target(目标对象):织入 Advice 的目标对象.,即被代理类
  • Weaving(织入):将 Aspect 和其他对象连接起来, 并创建 Adviced object 的过程。

1.通知(Advice)

通知定义了在切入点代码执行时间点附近需要做的工作。

Spring支持五种类型的通知:

  1. Before(前) org.apringframework.aop.MethodBeforeAdvice
  2. After-returning(返回后) org.springframework.aop.AfterReturningAdvice
  3. After-throwing(抛出后) org.springframework.aop.ThrowsAdvice
  4. Arround(周围) org.aopaliance.intercept.MethodInterceptor
  5. Introduction(引入) org.springframework.aop.IntroductionInterceptor

2.连接点(Joinpoint)

程序能够应用通知的一个“时机”,这些“时机”就是连接点,例如方法调用时、异常抛出时、方法返回后等等。

3.切入点(Pointcut)

通知定义了切面要发生的“故事”,连接点定义了“故事”发生的时机,那么切入点就定义了“故事”发生的地点。例如某个类或方法的名称,Spring中允许我们方便的用正则表达式来指定。

4.切面(Aspect)

通知、连接点、切入点共同组成了切面:时间、地点和要发生的“故事”。

5.引入(Introduction)

引入允许我们向现有的类添加新的方法和属性(Spring提供了一个方法注入的功能)。

6.目标(Target)

即被通知的对象,如果没有AOP,那么通知的逻辑就要写在目标对象中,有了AOP之后它可以只关注自己要做的事,解耦合!

7.代理(proxy)

应用通知的对象,详细内容参见设计模式里面的动态代理模式。

8.织入(Weaving)

把切面应用到目标对象来创建新的代理对象的过程,织入一般发生在如下几个时机:

(1)编译时:当一个类文件被编译时进行织入,这需要特殊的编译器才可以做的到,例如AspectJ的织入编译器;

(2)类加载时:使用特殊的ClassLoader在目标类被加载到程序之前增强类的字节代码;

(3)运行时:切面在运行的某个时刻被织入,SpringAOP就是以这种方式织入切面的,原理应该是使用了JDK的动态代理技术。

Spring AOP的实现机制

aop1

Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理。

JDK动态代理通过反射来接收被代理的类,并且要求被代理的类必须实现一个接口。JDK动态代理的核心是InvocationHandler接口和Proxy类。如果目标类没有实现接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。

CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成某个类的子类,注意,CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。

下面我们来了解一下什么是代理模式?

代理模式介绍

什么是代理模式?就是用一个新的对象来伪装原来的对象,从而实现一些“不可告人”的动作。

什么情况下会使用代理模式?简单来说,就是不能或者不想直接引用一个对象。什么是不能?比如我在内网中想访问外网的资源,但是因为网关的控制,访问不了。那什么是不想呢?比如我在网页上要显示一张图片,但是图片太大了,会拉慢页面的加载速度,我想用一张小一点的图片代替。

来看一张类结构图:

aop2

  • Subject:原对象的抽象
  • RealSubject:原对象的实现
  • Proxy: 代理对象

通过代理模式,客户端访问时同原来一样,但访问的前后已经做了额外的操作(可能你的信息和数据就被窃取了)。

好了,来看一个正常点的例子。做IT的一般都需要翻墙,比如去YouTube上看点MV啥的(说好的正常呢),但是正常访问肯定是要被屏蔽的,所以就要通过一些工具去穿过重重防守的GTW。一般的方式就是本地的工具将你的访问信息加密后,交给一个未被屏蔽的国外的服务器,然后服务器解密这些访问信息,去请求原始的访问地址,再将请求得到的资源和信息回传给你自己的本地。我们以浏览器来举例。

浏览器接口:

1
2
3
4
public interface Browser {

void visitInternet();
}

Chrome的实现类:

1
2
3
4
5
6
7
public class ChromeBrowser implements Browser{

public void visitInternet() {
System.out.println("visit YouTube");
}

}

如果直接访问肯定是要挂掉的,我们通过解密和加密的两个方法简单模拟翻墙的过程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class ChromeBrowser implements Browser{

public void visitInternet() {
encrypt();
System.out.println("visit YouTube");
decrypt();
}

// 加密
private void encrypt(){
System.out.println("encrypt ...");
}

// 解密
private void decrypt(){
System.out.println("decrypt ...");
}
}

总结:虽然这样就可以访问成功了,但直接将加密和解密的方式写死在原对象里,不仅侵入了原有的代码结构且会显得很LOW。

静态代理

根据上面的代理模式的类图,最简单的方式就是写一个静态代理,为ChromeBrowser写一个代理类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class ChromeBrowserProxy implements Browser{

private ChromeBrowser browser;

public ChromeBrowserProxy(ChromeBrowser chromeBrowser) {
this.browser = chromeBrowser;
}

public void visitInternet() {
encrypt();
browser.visitInternet();
decrypt();
}

// 加密
private void encrypt(){
System.out.println("encrypt ...");
}

// 解密
private void decrypt(){
System.out.println("decrypt ...");
}

}

ChromeBrowserProxy同样实现Browser接口,客户端访问时不再直接访问ChromeBrowser,而是通过它的代理类。

下面是静态代理的测试类:

1
2
3
4
5
6
7
public class StaticProxyTest {

public static void main(String[] args) {
Browser browser = new ChromeBrowserProxy(new ChromeBrowser());
browser.visitInternet();
}
}

总结:这种方式解决了对原对象的代码侵入,但是出现了另一个问题。如果我有好几个浏览器,难道每个浏览器的实现类都要写一个代理类吗?太LOW太LOW。我们需要更牛B的方式:JDK动态代理。

JDK动态代理

在JDK中提供了一种代理的实现方式,可以动态地创建代理类,就是java.lang.reflect包中的Proxy类提供的newProxyInstance方法。

1
Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
  • classLoader是创建代理类的类加载器
  • interfaces是原对象实现的接口
  • InvocationHandler是回调方法的接口

真正的代理过程通过InvocationHandler接口中的invoke方法来实现:

1
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
  • proxy是代理对象
  • method是执行的方法
  • args是执行方法的参数数组

还是以Chrome浏览器举例,JdkBrowserProxy实现InvocationHandler接口,并通过构造方法传入被代理的对象,然后在invoke方法中实现代理的过程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class JdkBrowserProxy implements InvocationHandler{

private Browser browser;

public JdkBrowserProxy(Browser browser) {
this.browser = browser;
}

public Browser getProxy(){
return (Browser) Proxy.newProxyInstance(browser.getClass().getClassLoader(),
browser.getClass().getInterfaces(), this);
}

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
encrypt();// 程序执行前加入逻辑,MethodBeforeAdviceInterceptor
Object retVal = method.invoke(browser, args);
decrypt();// 程序执行后加入逻辑,MethodAfterAdviceInterceptor
return retVal;
}

/**
* 加密
*/
private void encrypt(){
System.out.println("encrypt ...");
}

/**
* 解密
*/
private void decrypt(){
System.out.println("decrypt ...");
}
}

来看测试方法:

1
2
3
4
5
6
7
public class JdkDynamicProxyTest {

public static void main(String[] args) {
Browser browser = new JdkBrowserProxy(new ChromeBrowser()).getProxy();
browser.visitInternet();
}
}

总结:JDK的动态代理基本能够解决大部分的需求,唯一的缺点就是它只能代理接口中的方法。如果被代理对象没有实现接口,或者想代理没在接口中定义的方法,JDK的动态代理就无能为力了,此时就需要CGLIB动态代理。

CGLIB动态代理

cglib是一种强大的,高性能高品质的代码生成库,用来在运行时扩展JAVA的类以及实现指定接口。

通过cglib提供的Enhancer类的create静态方法来创建代理类。

1
Enhancer.create(Class type, Callback callback)
  • type是原对象的Class对象
  • callback是回调方法接口

cglib中的callback通过实现它的MethodInterceptor接口的intercept方法来进行回调:

1
public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args, MethodProxy proxy) throws Throwable;
  • obj是被代理的对象
  • method是执行的方法
  • args是执行方法的参数数组
  • proxy用来执行未被拦截的原方法

cglib代理类不局限于上面的浏览器的例子,而是通过泛型来实现通用,并且使用单例模式减少代理类的重复创建。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class CglibBrowserProxy implements MethodInterceptor{

private static CglibBrowserProxy proxy = new CglibBrowserProxy();

private CglibBrowserProxy(){

}

//获取静态实例的方法
public static CglibBrowserProxy getInstance(){
return proxy;
}

public <T> T getProxy(Class<T> clazz){
return (T) Enhancer.create(clazz, this);
}

public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
encrypt();// 程序执行前加入逻辑,MethodBeforeAdviceInterceptor
Object retVal = proxy.invokeSuper(obj, args);
decrypt();// 程序执行后加入逻辑,MethodAfterAdviceInterceptor
return retVal;
}

/**
* 加密
*/
private void encrypt(){
System.out.println("encrypt ...");
}

/**
* 解密
*/
private void decrypt(){
System.out.println("decrypt ...");
}
}

然后在ChromeBrowser添加一个听音乐的方法,它并未在Browser接口定义:

1
2
3
public void listenToMusic(){
System.out.println("listen to Cranberries");
}

来看下客户端测试:

1
2
3
4
5
6
7
8
public class CglibDynamicProxyTest {

public static void main(String[] args) {
ChromeBrowser browser = CglibBrowserProxy.getInstance().getProxy(ChromeBrowser.class);
browser.visitInternet();
browser.listenToMusic();
}
}

总结:可以发现没有使用Browser接口来接受代理对象,而是直接使用ChromeBrowser对象。这样的方式就可以代理ChromeBrowser中未在Chrome接口中的方法。

抛出问题:如果想让一个对象调用它未实现的接口中的方法,即后面AOP里所说的引用增强,原生的cglib怎么实现呢?

CGLIB引入增强

引入增强听上去很高大上,其实它的实现原理就以下几步:

  1. 通过CGLIB创建代理对象,并使其实现指定接口
  2. 在MethodIntercept的回调方法中,判断执行方法是否为接口中的方法,如果是,则通过反射调用接口的实现类。

创建一个新接口Game,它定义了开始的方法:

1
2
3
4
public interface Game {

void start();
}

让代理类实现Game接口,并在intercept方法中判断执行方法是接口方法还是原对象的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class CglibIntroductionBrowserProxy implements MethodInterceptor,Game{

private static CglibIntroductionBrowserProxy proxy = new CglibIntroductionBrowserProxy();

private CglibIntroductionBrowserProxy(){

}

public static CglibIntroductionBrowserProxy getInstance(){
return proxy;
}

public <T> T getProxy(Class<T> clazz){
return (T) Enhancer.create(clazz, new Class[]{ Game.class }, this);
}

public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
// 程序执行前加入逻辑,MethodBeforeAdviceInterceptor
Object retVal;
if(method.getDeclaringClass().isInterface()){
method.setAccessible(true);
retVal = method.invoke(this, args);
}else{
retVal = proxy.invokeSuper(obj, args);
}
// 程序执行后加入逻辑,MethodAfterAdviceInterceptor
return retVal;
}

public void start() {
System.out.println("start a game");
}

}

来看测试类:

1
2
3
4
5
6
7
8
9
10
public class CglibIntroductionDynamicProxyTest {

public static void main(String[] args) {
Browser browser = CglibIntroductionBrowserProxy.getInstance().getProxy(ChromeBrowser.class);
browser.visitInternet();

Game game = (Game) browser;
game.start();
}
}

总结:可以发现执行接口方法时,通过jdk的反射机制来实现的。而调用其自身方法,则是通过cglib来触发的。

最后补充几点

  1. JDK动态代理的代理对象只能通过接口去接收,如果用原对象接收,会报类型转换异常
  2. cglib不能拦截final修饰的方法,调用时只会执行原有方法
  3. cglib是在运行时通过操作字节码来完成类的扩展和改变,除了代理,还支持很多强大的操作,比如bean的生成和属性copy,动态创建接口以及融合多个对象等,具体见https://github.com/cglib/cglib/wiki/Tutorial

参考:

https://my.oschina.net/u/2377110/blog/1504596

https://my.oschina.net/u/2377110/blog/1504596

https://blog.csdn.net/dreamrealised/article/details/12885739

秉持初心,继续向前。
显示 Gitment 评论